#include "types.h"
#include "param.h"
#include "memlayout.h"
#include "mmu.h"
#include "proc.h"
#include "defs.h"
#include "x86.h"
#include "elf.h"

int
exec(char *path, char **argv)
{
  char *s, *last;   // 以下局部变量位于内核栈中
  int i, off;
  uint argc, sz, sp, ustack[3+MAXARG+1];
  struct elfhdr elf;
  struct inode *ip;
  struct proghdr ph;
  pde_t *pgdir, *oldpgdir;

  begin_op();
  if((ip = namei(path)) == 0){  // 根据文件名找到inode
    end_op();
    return -1;
  }
  ilock(ip);    // 对该inode加锁
  pgdir = 0;    // 本进程页表地址暂时设置为0

  // Check ELF header     // 检查是否为 ELF 格式文件
  if(readi(ip, (char*)&elf, 0, sizeof(elf)) < sizeof(elf))
    goto bad;
  if(elf.magic != ELF_MAGIC)
    goto bad;

  if((pgdir = setupkvm()) == 0)   // 页表初始化为与内核的scheduler相同
    goto bad;

  // Load program into memory.    // 根据可执行文件建立用户进程空间映像
  sz = 0;
  for(i=0, off=elf.phoff; i<elf.phnum; i++, off+=sizeof(ph)){ // 逐段读入
    if(readi(ip, (char*)&ph, off, sizeof(ph)) != sizeof(ph))
      goto bad;
    if(ph.type != ELF_PROG_LOAD)
      continue;
    if(ph.memsz < ph.filesz)
      goto bad;
    if(ph.vaddr + ph.memsz < ph.vaddr)
      goto bad;
    if((sz = allocuvm(pgdir, sz, ph.vaddr + ph.memsz)) == 0)  // 分配空间
      goto bad;
    if(ph.vaddr % PGSIZE != 0)
      goto bad;
    if(loaduvm(pgdir, (char*)ph.vaddr, ip, ph.off, ph.filesz) < 0)  // 载入
      goto bad;
  }
  iunlockput(ip);
  end_op();
  ip = 0;

  // Allocate two pages at the next page boundary.  // 分配隔离页帧和堆栈空间
  // Make the first inaccessible.  Use the second as the user stack.
  sz = PGROUNDUP(sz);
  if((sz = allocuvm(pgdir, sz, sz + 2*PGSIZE)) == 0)  // 分配两个页帧
    goto bad;
  clearpteu(pgdir, (char*)(sz - 2*PGSIZE));   // 第一个页帧用作隔离用途
  sp = sz;

  // Push argument strings, prepare rest of stack in ustack.
  for(argc = 0; argv[argc]; argc++) {   // 在堆栈中建立main()的命令行参数
    if(argc >= MAXARG)
      goto bad;
    sp = (sp - (strlen(argv[argc]) + 1)) & ~3;  // 将参数保存到堆栈中
    if(copyout(pgdir, sp, argv[argc], strlen(argv[argc]) + 1) < 0)
      goto bad;
    ustack[3+argc] = sp;
  }
  ustack[3+argc] = 0;

  ustack[0] = 0xffffffff;  // fake return PC
  ustack[1] = argc;
  ustack[2] = sp - (argc+1)*4;  // argv pointer

  sp -= (3+argc+1) * 4;
  if(copyout(pgdir, sp, ustack, (3+argc+1)*4) < 0)
    goto bad;

  // Save program name for debugging.
  for(last=s=path; *s; s++)
    if(*s == '/')
      last = s+1;
  safestrcpy(proc->name, last, sizeof(proc->name));

  // Commit to the user image.
  oldpgdir = proc->pgdir;       // 记录旧映像的页表（后面要释放空间）
  proc->pgdir = pgdir;          // 设置新进程映像的页表
  proc->sz = sz;                // 新进程空间的大小
  proc->tf->eip = elf.entry;  // main // 入口代码（xv6应用程序就是main()）
  proc->tf->esp = sp;           // 设置用户堆栈，返回到用户态时生效
  switchuvm(proc);              // 切换到新的进程空间（内核空间保存不变）

  /* lab2：共享内存实验 begin */
  shmrelease(oldpgdir, proc->shm, proc->shmkeymask);  // 回收共享内存
  /* lab2：共享内存实验 end */

  freevm(oldpgdir);             // 释放原理的页表空间

  /* lab2：共享内存实验 begin */
  proc->shm = KERNBASE;     // 重置 shm
  proc->shmkeymask = 0;     // 重置 shmkeymask
  /* lab2：共享内存实验 end */

  /* lab2：消息队列实验 begin */
  releasemq2(proc->mqmask);   //回收消息队列
  proc->mqmask = 0;          //重置进程的mqmask
  /* lab2：消息队列实验 end */

  return 0;

 bad:
  if(pgdir)
    freevm(pgdir);
  if(ip){
    iunlockput(ip);
    end_op();
  }
  return -1;
}
